Skip to content

fix(moa): restore virtual runtime after fallback - #53802

Closed
ildunari wants to merge 1 commit into
NousResearch:mainfrom
ildunari:fix/moa-restore-after-fallback-upstream
Closed

ildunari wants to merge 1 commit into
NousResearch:mainfrom
ildunari:fix/moa-restore-after-fallback-upstream

Conversation

@ildunari

Copy link
Copy Markdown

What does this PR do?

Fixes MoA primary-runtime restoration after provider fallback.

MoA is a virtual provider: it uses a MoAClient facade and intentionally does not have real OpenAI client kwargs. If a long-lived MoA session falls back to a concrete provider, then the next turn restores provider=model back to MoA. Before this fix, restoration could try to rebuild a real OpenAI client from MoA's empty/virtual client kwargs, causing restore failure instead of recreating the MoA facade.

The restore path now special-cases provider == "moa" and recreates MoAClient(agent.model or "default") instead of calling the OpenAI client builder.

Related Issue

No linked issue. Found while testing MoA sessions with fallback/restore behavior.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • Tests (adding or improving test coverage)

Changes Made

  • agent/agent_runtime_helpers.py
    • Rebuilds the virtual MoA facade when restoring the primary runtime to provider="moa".
    • Avoids constructing a real OpenAI client for MoA's virtual runtime.
  • tests/run_agent/test_moa_loop_mode.py
    • Adds a regression test that simulates fallback to Z.AI, then verifies primary restore returns to MoA without calling _create_openai_client.

How to Test

  1. python -m py_compile agent/agent_runtime_helpers.py
  2. pytest -q tests/run_agent/test_moa_loop_mode.py tests/run_agent/test_primary_runtime_restore.py

Results locally:

  • py_compile: passed
  • MoA loop + primary runtime restore tests: 40 passed
  • Additional adversarial review reran the focused restore test plus primary restore suite: 32 passed

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 26.5

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

The regression test guards the original failure mode by making _create_openai_client raise if the MoA restore path tries to build a real OpenAI client.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have labels Jun 27, 2026
@helix4u helix4u mentioned this pull request Jun 27, 2026
13 of 14 tasks

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for identifying the restore path; the current-main premise is valid: restore_primary_runtime() restores MoA's empty client_kwargs and then calls _create_openai_client() at agent/agent_runtime_helpers.py:1193-1198.

Problems

  • The proposed MoAClient(...) reconstruction drops the reference_callback installed during MoA initialization (agent/agent_init.py:846-889). That callback is the only route for moa.reference / moa.aggregating display events (agent/moa_loop.py:791-798, 937-958), so a restored MoA session would lose progress output.
  • The same virtual-runtime reconstruction remains in try_recover_primary_transport() at agent/agent_runtime_helpers.py:1001-1026; the conversation loop calls it before fallback (agent/conversation_loop.py:3925-3946). A MoA primary hitting a transient transport recovery would still attempt the empty-kwargs OpenAI rebuild.

Suggested changes

  • Reuse a shared MoA-facade factory or preserve the initialized callback when recreating the facade, and add an event-delivery regression test after restore.
  • Cover the primary transport-recovery sibling path with the same no-OpenAI-client invariant.

Automated hermes-sweeper review.

# OpenAI client kwargs; restoring it after a fallback must recreate
# the facade, not call OpenAI() with an empty api_key.
from agent.moa_loop import MoAClient

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MoA initialization passes reference_callback=_moa_reference_relay (agent/agent_init.py:846-889), but this reconstruction omits it. Please preserve that callback or use a shared facade factory; otherwise moa.reference and moa.aggregating progress events stop after a fallback restore.

@@ -1018,7 +1018,15 @@ def restore_primary_runtime(agent) -> bool:
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please apply the same virtual-MoA reconstruction to try_recover_primary_transport (agent/agent_runtime_helpers.py:1001-1026). It restores the same empty MoA snapshot and still calls _create_openai_client; the conversation loop invokes it before fallback for transient failures.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 15, 2026
teknium1 added a commit that referenced this pull request Jul 23, 2026
#53802)

Follow-up to the salvaged core of #53802: a naive MoAClient(preset) rebuild
restores a working facade but silently drops the reference_callback relay
wired in agent_init, so moa.reference / moa.aggregating display events stop
reaching every frontend for the rest of the session.

Introduce agent.moa_loop.build_moa_facade(agent, preset) as the single
construction point for the MoA facade and use it at:
- initial client construction (agent_init.py)
- turn-start fallback restore (restore_primary_runtime)
- transient transport recovery (try_recover_primary_transport — previously
  fell through to _create_openai_client with MoA's empty client_kwargs and
  died with 'api_key client option must be set')
- mid-session model switches (switch_model)

The relay reads agent.tool_progress_callback at emit time, so callbacks
attached after construction are picked up automatically.

Adds test_moa_restored_facade_still_emits_reference_events covering event
delivery through a restored facade.
teknium1 added a commit that referenced this pull request Jul 24, 2026
#53802)

Follow-up to the salvaged core of #53802: a naive MoAClient(preset) rebuild
restores a working facade but silently drops the reference_callback relay
wired in agent_init, so moa.reference / moa.aggregating display events stop
reaching every frontend for the rest of the session.

Introduce agent.moa_loop.build_moa_facade(agent, preset) as the single
construction point for the MoA facade and use it at:
- initial client construction (agent_init.py)
- turn-start fallback restore (restore_primary_runtime)
- transient transport recovery (try_recover_primary_transport — previously
  fell through to _create_openai_client with MoA's empty client_kwargs and
  died with 'api_key client option must be set')
- mid-session model switches (switch_model)

The relay reads agent.tool_progress_callback at emit time, so callbacks
attached after construction are picked up automatically.

Adds test_moa_restored_facade_still_emits_reference_events covering event
delivery through a restored facade.
@teknium1

Copy link
Copy Markdown
Collaborator

Merged via cluster PR #70280 (commit 74a56b7) — your core commit cherry-picked with authorship preserved, plus a follow-up that turns the fix into a shared build_moa_facade() factory used at init/restore/recover and preserves the reference_callback (a bare MoAClient() reconstruction would have silenced advisor display events after restore). Thanks for the restore-path diagnosis and regression test!

@teknium1 teknium1 closed this Jul 24, 2026
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Aug 2, 2026
- model_setup_flows: _model_flow_moa (always show presets),
  _model_flow_bedrock_api_key (mantle region endpoint)
- codex migration: _query_codex_plugins (app-server RPC)
- slack parser: build_slack_parser (manifest + write)
- delegate_tool: _resolve_child_credential_pool (custom endpoint identity NousResearch#7833)
- relay: _platform_is_fronted (back-compat alias)
- secrets_cli: cmd_token (verify-then-persist, 0. prefix warn)
- skills_hub: _github_publish (fork → branch → tree → PR)
- main: _tui_need_npm_install (content compare, prebuilt bundle),
  note_turn_start (interleave tripwire), _build_gateway_vbs_script
  (wscript no-console NousResearch#45599), _write_full_zip_backup (sqlite safe-copy),
  _login_openai_codex (device code + reuse), _frame_renderable (rich Group),
  systemd_install (legacy removal + --force pre-sync),
  build_moa_facade (reference relay NousResearch#53802), build_plugins_parser,
  _run_anthropic_oauth_flow (claude-code link), _render_distribution_plan
  (non-distribution warning), _detect_venv_python_processes (refuse-don't-kill)
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
NousResearch#53802)

Follow-up to the salvaged core of NousResearch#53802: a naive MoAClient(preset) rebuild
restores a working facade but silently drops the reference_callback relay
wired in agent_init, so moa.reference / moa.aggregating display events stop
reaching every frontend for the rest of the session.

Introduce agent.moa_loop.build_moa_facade(agent, preset) as the single
construction point for the MoA facade and use it at:
- initial client construction (agent_init.py)
- turn-start fallback restore (restore_primary_runtime)
- transient transport recovery (try_recover_primary_transport — previously
  fell through to _create_openai_client with MoA's empty client_kwargs and
  died with 'api_key client option must be set')
- mid-session model switches (switch_model)

The relay reads agent.tool_progress_callback at emit time, so callbacks
attached after construction are picked up automatically.

Adds test_moa_restored_facade_still_emits_reference_events covering event
delivery through a restored facade.
prmartinow pushed a commit to prmartinow/hermes-agent that referenced this pull request Aug 26, 2026
NousResearch#53802)

Follow-up to the salvaged core of NousResearch#53802: a naive MoAClient(preset) rebuild
restores a working facade but silently drops the reference_callback relay
wired in agent_init, so moa.reference / moa.aggregating display events stop
reaching every frontend for the rest of the session.

Introduce agent.moa_loop.build_moa_facade(agent, preset) as the single
construction point for the MoA facade and use it at:
- initial client construction (agent_init.py)
- turn-start fallback restore (restore_primary_runtime)
- transient transport recovery (try_recover_primary_transport — previously
  fell through to _create_openai_client with MoA's empty client_kwargs and
  died with 'api_key client option must be set')
- mid-session model switches (switch_model)

The relay reads agent.tool_progress_callback at emit time, so callbacks
attached after construction are picked up automatically.

Adds test_moa_restored_facade_still_emits_reference_events covering event
delivery through a restored facade.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
NousResearch#53802)

Follow-up to the salvaged core of NousResearch#53802: a naive MoAClient(preset) rebuild
restores a working facade but silently drops the reference_callback relay
wired in agent_init, so moa.reference / moa.aggregating display events stop
reaching every frontend for the rest of the session.

Introduce agent.moa_loop.build_moa_facade(agent, preset) as the single
construction point for the MoA facade and use it at:
- initial client construction (agent_init.py)
- turn-start fallback restore (restore_primary_runtime)
- transient transport recovery (try_recover_primary_transport — previously
  fell through to _create_openai_client with MoA's empty client_kwargs and
  died with 'api_key client option must be set')
- mid-session model switches (switch_model)

The relay reads agent.tool_progress_callback at emit time, so callbacks
attached after construction are picked up automatically.

Adds test_moa_restored_facade_still_emits_reference_events covering event
delivery through a restored facade.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants